using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Diagnostics;
using System.ComponentModel;
namespace Lizk.SimpleHUD
{
    public class HUDFont
    {
        private string fontFamily;
        private float fontSize;
        private bool bold, italic, unicode, smoothing, loaded;
        private string charset;
        private int heightStretch;
        private int antialias;
        private int padUp, padRight, padDown, padLeft;

        public int PadLeft
        {
            get { return padLeft; }
            set { padLeft = value; }
        }

        public int PadDown
        {
            get { return padDown; }
            set { padDown = value; }
        }

        public int PadRight
        {
            get { return padRight; }
            set { padRight = value; }
        }

        public int PadUp
        {
            get { return padUp; }
            set { padUp = value; }
        }
        private int xSpacing, ySpacing;

        public int YSpacing
        {
            get { return ySpacing; }
            set { ySpacing = value; }
        }

        public int XSpacing
        {
            get { return xSpacing; }
            set { xSpacing = value; }
        }

        private int lineHeight;

        public int LineHeight
        {
            get { return lineHeight; }
            set { lineHeight = value; }
        }
        private int lineBase;
        private int scaleHeight;
        private int scaleWidth;
        private bool packed;

        


        private Dictionary<int, BMChar> glyphs = new Dictionary<int, BMChar>();
        private Dictionary<int, string> pages = new Dictionary<int, string>();
        private Dictionary<KeyValuePair<int, int>, int> kerning = new Dictionary<KeyValuePair<int, int>, int>();
        private string name;

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public Dictionary<int, string> Pages { get { return pages; } }

        private HUDFont(string name)
        {
            this.name = name;
        }

        public bool IsLoaded
        {
            get { return loaded; }
        }

        public static List<HUDFont> Fonts
        {
            get
            {
                List<HUDFont> fts = new List<HUDFont>();
                fts.AddRange(fonts.Values);
                return fts;
            }
        }



        private static Dictionary<string, HUDFont> fonts = new Dictionary<string, HUDFont>();

        public static HUDFont GetFont(string fontName)
        {
            if (fonts.ContainsKey(fontName))
                return fonts[fontName];
            else
            {
                HUDFont font = new HUDFont(fontName);
                fonts.Add(fontName, font);
                return font;
            }
        }


        public void LoadFromFile2(XmlDocument xml)
        {
            XmlNodeList xnl = xml.GetElementsByTagName("glyph");
            foreach (XmlNode n in xnl)
            {
                BMChar bc = new BMChar();
                foreach (XmlAttribute a in n.Attributes)
                {
                    string[] parts;
                    switch (a.Name)
                    {

                        case "code":
                            bc.Id = Int32.Parse(a.Value, System.Globalization.NumberStyles.HexNumber);
                            break;
                        case "origin":
                            parts = a.Value.Split(',');
                            bc.x = UInt16.Parse(parts[0]);
                            bc.y = UInt16.Parse(parts[1]);
                            break;
                        case "size":
                            parts = a.Value.Split('x');
                            bc.Width = UInt16.Parse(parts[0]);
                            bc.Height = UInt16.Parse(parts[1]);
                            break;
                        case "lsb":
                           // bc.xOffset = Int16.Parse(a.Value);
                            //bc.xOffset *= 2;
                            break;
                        case "aw":
                            bc.xAdvance = Int16.Parse(a.Value);
                            break;
                        case "bm":
                            bc.Page = UInt16.Parse(a.Value);
                            break;
                    }
                }
                glyphs.Add(bc.Id, bc);
            }


            xnl = xml.GetElementsByTagName("bitmap");
            foreach (XmlNode n in xnl)
            {
                int id = -1;
                string pagefile = null;
                foreach (XmlAttribute a in n.Attributes)
                {
                    switch (a.Name)
                    {
                        case "id":
                            id = Int32.Parse(a.Value);
                            break;
                        case "name":
                            pagefile = a.Value;
                            break;
                    }
                }
                if (id > -1)
                    Pages.Add(id, pagefile);
            }


            xnl = xml.GetElementsByTagName("font");
            foreach (XmlNode n in xnl)
            {
                foreach (XmlAttribute a in n.Attributes)
                {
                    switch (a.Name)
                    {
                        case "height":
                            lineHeight = Int32.Parse(a.Value);
                            break;
                        case "base":
                            lineBase = Int32.Parse(a.Value);
                            break;
                        case "size":
                            string[] parts = a.Value.Split('x');
                            scaleWidth = Int32.Parse(parts[0]);
                            scaleHeight = Int32.Parse(parts[1]);
                            break;
                    }
                }
            }
            loaded = true;
        }




        public void LoadFromFile(XmlDocument xml)
        {
            XmlNodeList xnl = xml.GetElementsByTagName("char");
            foreach (XmlNode n in xnl)
            {
                BMChar bc = new BMChar();
                foreach (XmlAttribute a in n.Attributes)
                {
                    switch (a.Name)
                    {
                        case "id":
                            bc.Id = UInt16.Parse(a.Value);
                            break;
                        case "x":
                            bc.x = UInt16.Parse(a.Value);
                            break;
                        case "y":
                            bc.y = UInt16.Parse(a.Value);
                            break;
                        case "width":
                            bc.Width = UInt16.Parse(a.Value);
                            break;
                        case "height":
                            bc.Height = UInt16.Parse(a.Value);
                            break;
                        case "xoffset":
                            bc.xOffset = Int16.Parse(a.Value);
                            break;
                        case "yoffset":
                            bc.yOffset = Int16.Parse(a.Value);
                            break;
                        case "xadvance":
                            bc.xAdvance = Int16.Parse(a.Value);
                            break;
                        case "page":
                            bc.Page = UInt16.Parse(a.Value);
                            break;
                        case "chnl":
                            bc.Channel = UInt16.Parse(a.Value);
                            break;
                    }
                }
                glyphs.Add(bc.Id, bc);
            }
            xnl = xml.GetElementsByTagName("info");
            foreach (XmlNode n in xnl)
            {
                foreach (XmlAttribute a in n.Attributes)
                {
                    switch (a.Name)
                    {
                        case "face":
                            fontFamily = a.Value;
                            break;
                        case "charset":
                            charset = a.Value;
                            break;
                        case "size":
                            fontSize = Int32.Parse(a.Value);
                            break;
                        case "stretchH":
                            heightStretch = Int32.Parse(a.Value);
                            break;
                        case "aa":
                            antialias = Int32.Parse(a.Value);
                            break;
                        case "bold":
                            bold = UInt16.Parse(a.Value) != 0; 
                            break;
                        case "italic":
                            italic = UInt16.Parse(a.Value) != 0;
                            break;
                        case "unicode":
                            unicode = UInt16.Parse(a.Value) != 0;
                            break;
                        case "smooth":
                            smoothing = UInt16.Parse(a.Value) != 0;
                            break;
                        case "padding":
                            string[] split = a.Value.Split(',');
                            padUp = Int32.Parse(split[0]);
                            padRight = Int32.Parse(split[1]);
                            padDown = Int32.Parse(split[2]);
                            padLeft = Int32.Parse(split[3]);
                            break;
                        case "spacing":
                            split = a.Value.Split(',');
                            xSpacing = Int32.Parse(split[0]);
                            ySpacing = Int32.Parse(split[1]);
                            break;
                    }
                }
            }
            
            xnl = xml.GetElementsByTagName("common");
            foreach (XmlNode n in xnl)
            {
                foreach (XmlAttribute a in n.Attributes)
                {
                    switch (a.Name)
                    {
                        case "lineHeight":
                            lineHeight = Int32.Parse(a.Value);
                            break;
                        case "base":
                            lineBase = Int32.Parse(a.Value);
                            break;
                        case "scaleW":
                            scaleWidth = Int32.Parse(a.Value);
                            break;
                        case "scaleH":
                            scaleHeight = Int32.Parse(a.Value);
                            break;
                        case "packed":
                            packed = UInt16.Parse(a.Value) != 0;
                            break;
                    }
                }
            }


            xnl = xml.GetElementsByTagName("page");
            foreach (XmlNode n in xnl)
            {
                int id = -1;
                string pagefile = null;
                foreach (XmlAttribute a in n.Attributes)
                {
                    switch (a.Name)
                    {
                        case "id":
                            id = Int32.Parse(a.Value);
                            break;
                        case "file":
                            pagefile = a.Value;
                            break;                    }
                }
                if(id > -1)
                    Pages.Add(id, pagefile);
            }

            xnl = xml.GetElementsByTagName("kerning");
            foreach (XmlNode n in xnl)
            {
                int first = -1,second = -1, amount = 0;
                foreach (XmlAttribute a in n.Attributes)
                {
                    switch (a.Name)
                    {
                        case "first":
                            first = Int32.Parse(a.Value);
                            break;
                        case "second":
                            second = Int32.Parse(a.Value);
                            break;
                        case "amount":
                            amount = Int32.Parse(a.Value);
                            break;
                    }
                }
                if (first > -1)
                    kerning.Add(new KeyValuePair<int, int>(first, second), amount);
            }
            loaded = true;
        }

        private bool ContainsKerning(int first, int second)
        {
            return kerning.ContainsKey(new KeyValuePair<int, int>(first, second));
        }

        private int GetKerning(int first, int second)
        {
            return kerning[new KeyValuePair<int, int>(first, second)];
        }

        private int CalcKern(char chLeft, char chRight)
        {
            if (ContainsKerning(chLeft, chRight))
            {
                return GetKerning(chLeft, chRight);
            }
            return 0;
        }

        private void Ellipses(int pxMaxWidth, string str, string endString, out string outStr, out int pxWidth)
        {
            char cLast = '\0';

            int endLength = 0;
            int pxLastWidth = 0;
            pxWidth = 0;
            outStr = str;
            /*
             * Psuedo:
             * 
             * GetWidth of endstring
             * 
             *  
             * 
             * 
             * 
             */

            foreach (char c in endString)
            {
                // if this is a newline, then return. the width is set correctly
                if (c == '\n')
                    break;

                if (!glyphs.ContainsKey(c))
                {
                    //TODO: print out undefined char glyph
                    continue;
                }

                BMChar ginfo = glyphs[c];

                // if kerning is enabled, get the kern adjustment for this char pair
                if (true)
                {
                    int pxKern = CalcKern(cLast, c);
                    endLength += pxKern;
                    cLast = c;
                }

                // update the string width and char count
                endLength += ginfo.xAdvance;
            }

            for (int i = 0; i < str.Length; i++)
            {

                // if this is a newline, then return. the width is set correctly                
                if (!glyphs.ContainsKey(str[i]))
                {
                    //TODO: print out undefined char glyph
                    continue;
                }

                BMChar ginfo = glyphs[str[i]];

                // if kerning is enabled, get the kern adjustment for this char pair
                if (true)
                {
                    int pxKern = CalcKern(cLast, str[i]);
                    pxWidth += pxKern;
                }

                // update the string width and char count
                pxLastWidth = pxWidth;
                pxWidth += ginfo.xAdvance;
                cLast = str[i];
                // if we've exceeded the max, then return the chars up to the last complete word
                if (pxWidth + endLength > pxMaxWidth || str[i] == '\n')
                {
                    pxWidth = pxLastWidth + endLength;
                    outStr = str.Substring(0, i) + endString;
                    return;
                }
            }
        }

        [Serializable]
        public struct DrawInfo
        {
            public RectangleS Source;
            public RectangleS Destination;
            public ColorS Color;
            public int Page;
            public DrawInfo(RectangleS source, RectangleS dest, ColorS color, int page)
            {
                Source = source;
                Destination = dest;
                Color = color;
                this.Page = page;
            }
        }

        public List<DrawInfo> TextBox2(RectangleS r, ColorS color, Alignment alignment, bool wrap, string str)
        {
            /*
             * Features:
             *  Clipping (Requires changes to the internal DrawString function, maybe an alternate)
             *  Ellipses
             *  Alignment
             *  Bolding (Requires changes to BitmapFont and the files created for it.)
             * 
             *  Ellipses Psuedo
             *      if line too wide
             *          do
             *              remove a char and calc line length with 3 dots
             *          while line too wide
             * 
             *  Alignment
             *      Do breaking and sort by lines
             *      
             *      how many lines are there room for
             *      how many lines are there
             *      if there are more lines than there is room for
             *          set line amount to the amount there is room for
             *      else
             *          set line amount to how many there are
             * 
             *      switch vertical alignment
             *          case middle:
             *              linestart = linelist.count / 2 - lineamount / 2
             *              drawstart = drawRect.height / 2f - lineamount / 2f * lineheight
             *          case bottom
             *              drawstart = drawRect.bottom - lineamount * lineHeight
             *          case top
             *              drawstart = drawRect.top;
             * 
             *      for(linestart; lineamount; +1)
             *          drawline line[i]
             *          y += case bottom ? -1*height : height
             * 
             *  Breaking
             *      
             * 
            */
            int nChars;
            int pxWidth;
            PointS vAt = new PointS(r.Left, r.Top);
            List<DrawInfo> result = new List<DrawInfo>();
            List<string> lines = new List<string>();
            List<int> lineWidth = new List<int>();
            while (str.Length != 0)
            {
                // stop drawing if there isn't room for this line
                if (vAt.Y + lineHeight > r.Bottom)
                    break;

                if (wrap)
                {
                    CountCharWidth((int)r.Width - padLeft - padRight, str, out nChars, out pxWidth);
                    lines.Add(str.Substring(0, nChars));
                    lineWidth.Add(pxWidth);
                    str = str.Substring(nChars);
                    vAt.Y += lineHeight;
                }
                else
                {
                    string s;
                    Ellipses((int)r.Width, str, "...", out s, out pxWidth);
                    lines.Add(s);
                    lineWidth.Add(pxWidth);
                    break;
                }

            }
            int linestart = 0;
            int step = 1;

            if ((alignment & Alignment.Bottom) == Alignment.Bottom)
            {
                vAt.Y = r.Bottom - lineHeight - padDown;
//                linestart = lines.Count - 1;
                step = -1;
            }
            else if ((alignment & Alignment.VCenter) == Alignment.VCenter)
            {
                vAt.Y = r.Top + (float)r.Height / 2f - (float)lines.Count / 2f * (float)lineHeight;
//                linestart = lines.Count / 2;
            }
            else
            {
                vAt.Y = r.Top + padUp;
            }

            for (int i = linestart; i < lines.Count && i >= 0; i += step)
            {
                if ((alignment & Alignment.Center) == Alignment.Center)
                {
                    vAt.X = r.Left + ((r.Width - lineWidth[i]) / 2);
                }
                else if ((alignment & Alignment.Right) == Alignment.Right)
                {
                    vAt.X = r.Left + (r.Width - lineWidth[i]) - padRight;
                }
                else
                {
                    vAt.X = r.Left + padLeft;
                }
                vAt = new PointS((int)vAt.X, (int)vAt.Y);
                DrawString_internal(vAt, color, lines[i], result);
                vAt.Y += lineHeight * step;
            }
            return result;
        }

        /// <summary>
        /// Private version of DrawString that expects the string to be formatted already
        /// </summary>
        /// <param name="vAt">(x,y) coord</param>
        /// <param name="cText">Text color</param>
        /// <param name="str">String</param>
        /// <returns>Width of string (in pixels)</returns>
        private int DrawString_internal(PointS vAt, ColorS cText, string str, List<DrawInfo> result)
        {
            PointS vOrigin = new PointS(0, 0);
            int pxWidth = 0;
            char cLast = '\0';

            // draw each character in the string
            foreach (char c in str)
            {
                if (!glyphs.ContainsKey(c))
                {
                    //TODO: print out undefined char glyph
                    continue;
                }

                BMChar ginfo = glyphs[c];

                // if kerning is enabled, get the kern adjustment for this char pair
                if (true)
                {
                    int pxKern = CalcKern(cLast, c);
                    vAt.X += pxKern;
                    pxWidth += pxKern;
                    cLast = c;
                }

                // draw the glyph
                vAt.X += ginfo.xOffset;
                if (ginfo.Width != 0 && ginfo.Height != 0)
                {
                    RectangleS rSource = new RectangleS(ginfo.x - padLeft, ginfo.y - padUp, ginfo.Width + padLeft + padRight, ginfo.Height + padUp + padDown);
                    result.Add(new DrawInfo(rSource, new RectangleS(new PointS(vAt.X, vAt.Y + ginfo.yOffset), rSource.Size), cText, ginfo.Page));
                    //sb.Draw(m_dictBitmapID2Texture[ginfo.nBitmapID], vAt, rSource, color, 0.0f, vOrigin, 1.0f, SpriteEffects.None, m_fpDepth);
                }

                // update the string width and advance the pen to the next drawing position
                pxWidth += ginfo.xAdvance;
                vAt.X += ginfo.xAdvance - ginfo.xOffset;
            }

            // record final pen position and color
//            m_vPen = vAt;
//            m_color = cText;

            return pxWidth;
        }


        /// <summary>
        /// Calculate the number of characters that fit in the given width.
        /// </summary>
        /// <param name="pxMaxWidth">Maximum string width</param>
        /// <param name="str">String</param>
        /// <param name="nChars">Number of characters that fit</param>
        /// <param name="pxWidth">Width of substring</param>
        private void CountCharWidth(int pxMaxWidth, string str, out int nChars, out int pxWidth)
        {
            int nLastWordBreak = 0;
            int pxLastWordBreakWidth = 0;
            int pxLastWidth = 0;
            char cLast = '\0';

            nChars = 0;
            pxWidth = 0;

            foreach (char c in str)
            {
                // if this is a newline, then return. the width is set correctly
                if (c == '\n')
                {
                    nChars++;
                    return;
                }

                if (!glyphs.ContainsKey(c))
                {
                    //TODO: print out undefined char glyph
                    continue;
                }

                BMChar ginfo = glyphs[c];

                // if kerning is enabled, get the kern adjustment for this char pair
                if (true)
                {
                    int pxKern = CalcKern(cLast, c);
                    pxWidth += pxKern;
                    cLast = c;
                }

                // update the string width and char count
                pxLastWidth = pxWidth;
                pxWidth += ginfo.xAdvance;
                nChars++;

                // record the end of the previous word if this is a whitespace char
                if (Char.IsWhiteSpace(c))
                {
                    nLastWordBreak = nChars;			// include space in char count
                    pxLastWordBreakWidth = pxLastWidth;	// don't include space in width
                }

                // if we've exceeded the max, then return the chars up to the last complete word
                if (pxWidth > pxMaxWidth)
                {
                    pxWidth = pxLastWordBreakWidth;
                    if (pxWidth == 0)
                    {
                        // fallback to last char if we haven't seen a complete word
                        pxWidth = pxLastWidth;
                        nChars--;
                    }
                    else
                        nChars = nLastWordBreak;
                    return;
                }
            }
        }


    
    
    
    }
    

    public struct BMChar
    {
        public int Id;
        public ushort x;
        public ushort y;
        public ushort Width;
        public ushort Height;
        public short yOffset;
        public short xOffset;
        public short xAdvance;
        public ushort Page;
        public ushort Channel;
    }


}
